package com.ysb.searchserver;

import java.io.File;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.LongField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.NumericRangeQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.spans.SpanNearQuery;
import org.apache.lucene.search.spans.SpanQuery;
import org.apache.lucene.search.spans.SpanTermQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.MMapDirectory;
import org.apache.lucene.util.Version;

import com.ysb.util.PropertyConfig;

public abstract class ExamSearcher {
	
	private String indexPath = null;
	
	private final static String SENTENCE_FIELD_NAME = "sen";
	
	private final static String ID_FIELD_NAME = "id";
	private final static String SORT_FIELD_NAME = "sort";
	
	private IndexReader indexReader = null;
	
	private IndexWriter indexWriter = null;
	
	private Sort timesorter = new Sort(new SortField(SORT_FIELD_NAME, SortField.Type.LONG, true));//时间逆序
	
	private Directory readerDirectory;
	
	private LastIdSaver idSaver;
	
	public ExamSearcher() throws Throwable{
		PropertyConfig config = new PropertyConfig("lucene", "utf-8");
		indexPath = config.get("indexpath", null);
		if(indexPath == null) {
			throw new RuntimeException("no lucene.properties in classpath");
		}
		new File(indexPath).mkdirs();
		idSaver = new LastIdSaver(indexPath+"/idsaver");
		Directory writeDirectory = FSDirectory.open(new File(indexPath));
		IndexWriterConfig iwc=new IndexWriterConfig(Version.LATEST, this.getAnalyzer());
		indexWriter = new IndexWriter(writeDirectory, iwc);
		createIndex();
		ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
		IndexUpdator indexUpdator = new IndexUpdator();
		service.scheduleAtFixedRate(indexUpdator, getNextIntervalDelay(), 86400, TimeUnit.SECONDS);
	}
	
	//每天4点提交索引更新
	private long getNextIntervalDelay() {
		Calendar cal = Calendar.getInstance();
		long now = cal.getTimeInMillis();
		cal.add(Calendar.DAY_OF_MONTH, 1);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.HOUR_OF_DAY, 2);
		return cal.getTimeInMillis() - now;
	}
	
	protected abstract Analyzer getAnalyzer();
	
	protected void createIndex() throws Throwable {
		File[] files = new File(indexPath).listFiles();
		List<ExamSentence> examSentences = null;
		if(files == null || files.length <= 1) {
			examSentences = getExamSentences(-1);
		}
		else {
			int id = idSaver.get(-1);
			examSentences = getExamSentences(id);
		}
		int maxId = -1;
		for(ExamSentence examSentence:examSentences) {
			this.deleteDoc(examSentence.uniqId);
			this.addDoc(examSentence);
			if(examSentence.uniqId > maxId) {
				maxId = examSentence.uniqId;
			}
		}
		if(!examSentences.isEmpty()) {
			commit();
			idSaver.save(maxId);
			reloadIndexReader();
		}
	}
	
	public void reloadIndexReader() throws Throwable {
		Directory newReaderDirecotry = MMapDirectory.open(new File(indexPath));
		IndexReader newReader = DirectoryReader.open(newReaderDirecotry);
		IndexReader oldReader = this.indexReader;
		Directory oldReaderDirectory = this.readerDirectory;
		this.indexReader = newReader;
		this.readerDirectory = newReaderDirecotry;
		if(oldReader != null) {
			oldReader.close();
		}
		if(oldReaderDirectory != null) {
			oldReaderDirectory.close();
		}
	}
	
	protected abstract List<ExamSentence> getExamSentences(int id) throws Throwable;
	
	/**
	 * 创建索引
	 * @param analyzer
	 * @throws Exception
	 */
//	public void createIndex() throws Throwable{
//		File[] files = new File(indexPath).listFiles();
//		if(files == null || files.length <= 1) {
//			addDoc(2014, "Internet shopping is a new way of shopping. many students offers a lot of advantages. The most important one is convenience. You can shop whenever you like because the online shops are open 24 hours a day. And you don’t have to queue with others", 0);
//			addDoc(2015, "As you know, many students like to work on the Internet. Of course, it can bring us many good things. We can learn more knowledge outside our books, practice our spoken English with someone else and look up some useful information.", 1);
//		}
//		commit();
//		Directory readerDirecotry = MMapDirectory.open(new File(indexPath));
//		indexReader = DirectoryReader.open(readerDirecotry);
//		System.out.println("11111==="+search("Many students"));
//		IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//		TopDocs td = indexSearcher.search(NumericRangeQuery.newIntRange(ID_FIELD_NAME, 1, 1, true, true), 10, timesorter);
//		System.out.println(Arrays.toString(td.scoreDocs));
//		deleteDoc(1);
//		commit();
//		indexReader = DirectoryReader.open(readerDirecotry);
//		System.out.println("22222==="+search("Many students"));
//		addDoc(2013, "As you know, many students like to work on the Internet. Of course, it can bring us many good things. We can learn more knowledge outside our books, practice our spoken English with someone else and look up some useful information.", 1);
//		commit();
//		indexReader = DirectoryReader.open(readerDirecotry);
//		System.out.println("33333==="+search("Many students"));
//		indexWriter.close();
//	}
	
	public void deleteDoc(int uniqid) throws Throwable {
		Query query = NumericRangeQuery.newIntRange(ID_FIELD_NAME, uniqid, uniqid, true, true);
		indexWriter.deleteDocuments(query);
	}
	
	public void updateDoc(ExamSentence examSentence) throws Throwable {
		indexWriter.updateDocument(new Term(ID_FIELD_NAME, String.valueOf(examSentence.getUniqId())), examSentence.toDoc());
	}
	
	private Document genDoc(ExamSentence examSentence) {
		return examSentence.toDoc();
	}
	
	/**
	 * 动态添加Document
	 * @param iw
	 * @throws Exception
	 */
	public void addDoc(ExamSentence examSentence)  throws Throwable {
		Document doc = genDoc(examSentence);
		indexWriter.addDocument(doc);
	}
	
	private void commit() throws Throwable {
		this.indexWriter.commit();
	}
	
	private SpanNearQuery getSpanQuery(String queryWords) throws Throwable {
		Analyzer analyzer = this.getAnalyzer();
		TokenStream tokenStream = analyzer.tokenStream(SENTENCE_FIELD_NAME, queryWords);
		CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
		tokenStream.reset();
		String term = null;
		ArrayList<String> terms = new ArrayList<String>();
		while (tokenStream.incrementToken()) {
		    term = charTermAttribute.toString();
		    terms.add(term);
		}
		SpanQuery[] sqs = new SpanQuery[terms.size()];
		for(int i=0;i<sqs.length;i++) {
			sqs[i] = new SpanTermQuery(new Term(SENTENCE_FIELD_NAME, terms.get(i)));
		}
		analyzer.close();
		return new SpanNearQuery(sqs, 0, true);
	}
	/**
	 * 搜索
	 * @param query
	 * @throws Throwable 
	 */
	public List<Integer> search(String queryWords) throws Throwable {
		IndexSearcher indexSearcher = new IndexSearcher(indexReader);
		TopDocs td = indexSearcher.search(getSpanQuery(queryWords), 10, timesorter);
		ScoreDoc[] sds =td.scoreDocs;
		List<Integer> result = new ArrayList<Integer>(sds.length);
		Document doc = null;
		for (ScoreDoc sd : sds) { 
			doc = indexSearcher.doc(sd.doc); 
			result.add(doc.getField(ID_FIELD_NAME).numericValue().intValue());
		}
		return result;
	}
	
	public static class ExamSentence {
		private int uniqId;//主键
		private long examId;//标识时间，用于逆序排序
		private String sentence;
		
		public ExamSentence(int uniqId, long examId, String sentence) {
			this.uniqId = uniqId;
			this.examId = examId;
			this.sentence = sentence;
		}
		
		public Document toDoc() {
			Document doc = new Document();
			doc.add(new LongField(SORT_FIELD_NAME, examId, Store.NO));
			doc.add(new TextField(SENTENCE_FIELD_NAME, sentence, Store.NO));
			IntField idField = new IntField(ID_FIELD_NAME, uniqId, Store.YES);
			doc.add(idField);
			return doc;
		}

		public int getUniqId() {
			return uniqId;
		}

		public long getExamId() {
			return examId;
		}

		public String getSentence() {
			return sentence;
		}
		
		
	}
	private class IndexUpdator implements Runnable {
		public void run() {
			try {
				reloadIndexReader();
			} catch (Throwable e) {
				BizLogger.error(e, "IndexUpdator run error");
			}
		}
	}
}